{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:49.075533Z",
     "start_time": "2025-01-21T09:48:49.069162Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:32.727044Z",
     "iopub.status.busy": "2025-01-21T12:01:32.726867Z",
     "iopub.status.idle": "2025-01-21T12:01:34.847294Z",
     "shell.execute_reply": "2025-01-21T12:01:34.846759Z",
     "shell.execute_reply.started": "2025-01-21T12:01:32.727020Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2cc8618b-bc74-4a63-85ff-9126774f5d97",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.848773Z",
     "iopub.status.busy": "2025-01-21T12:01:34.848284Z",
     "iopub.status.idle": "2025-01-21T12:01:34.851379Z",
     "shell.execute_reply": "2025-01-21T12:01:34.850679Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.848751Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !pip list|grep kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f9b0cb8a-c60f-4ede-88e4-e51b12184288",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.852449Z",
     "iopub.status.busy": "2025-01-21T12:01:34.852090Z",
     "iopub.status.idle": "2025-01-21T12:01:34.855177Z",
     "shell.execute_reply": "2025-01-21T12:01:34.854543Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.852419Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !pip install kaggle # 安装kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c7179abe-df5b-4475-aca0-6e0c5e7b74d5",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.856270Z",
     "iopub.status.busy": "2025-01-21T12:01:34.855880Z",
     "iopub.status.idle": "2025-01-21T12:01:34.858815Z",
     "shell.execute_reply": "2025-01-21T12:01:34.858199Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.856241Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "44021815-6d6f-46f6-8602-da73340a2025",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.861045Z",
     "iopub.status.busy": "2025-01-21T12:01:34.860696Z",
     "iopub.status.idle": "2025-01-21T12:01:34.863668Z",
     "shell.execute_reply": "2025-01-21T12:01:34.863144Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.861017Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# # 在非kaggle平台上使用kaggle的Dataset\n",
    "# import json\n",
    "# token = {\"username\":\"xuyouhao\",\"key\":\"1dd7e72fbca26a1fc9996ef59ca26d9e\"}\n",
    "# with open('/mnt/workspace/kaggle.json', 'w') as file:\n",
    "#   json.dump(token, file)#json.dump类似于write"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "e65804fd-2b99-48a4-998e-70497fc459ca",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.864326Z",
     "iopub.status.busy": "2025-01-21T12:01:34.864146Z",
     "iopub.status.idle": "2025-01-21T12:01:34.866652Z",
     "shell.execute_reply": "2025-01-21T12:01:34.866134Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.864307Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !cat /mnt/workspace/kaggle.json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "44e12f93-4207-4558-87d4-d71f5fcf2b09",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.867460Z",
     "iopub.status.busy": "2025-01-21T12:01:34.867171Z",
     "iopub.status.idle": "2025-01-21T12:01:34.869583Z",
     "shell.execute_reply": "2025-01-21T12:01:34.869112Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.867441Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# # 无脑使用\n",
    "# !mkdir -p ~/.kaggle\n",
    "# !cp /mnt/workspace/kaggle.json ~/.kaggle/\n",
    "# !chmod 600 ~/.kaggle/kaggle.json\n",
    "# !kaggle config set -n path -v /content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "82fbeffb-eb70-426d-9270-ea00049637dd",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.870381Z",
     "iopub.status.busy": "2025-01-21T12:01:34.870203Z",
     "iopub.status.idle": "2025-01-21T12:01:34.872636Z",
     "shell.execute_reply": "2025-01-21T12:01:34.872101Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.870363Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !ls /content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "34c290c0-28e9-4758-a617-d74f95e0825c",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.873442Z",
     "iopub.status.busy": "2025-01-21T12:01:34.873142Z",
     "iopub.status.idle": "2025-01-21T12:01:34.875505Z",
     "shell.execute_reply": "2025-01-21T12:01:34.875049Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.873423Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !kaggle competitions download -c cifar-10 #下载比赛数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a4a9214f-ceb2-4345-9afa-8f908556abc7",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.876367Z",
     "iopub.status.busy": "2025-01-21T12:01:34.876075Z",
     "iopub.status.idle": "2025-01-21T12:01:34.878527Z",
     "shell.execute_reply": "2025-01-21T12:01:34.877949Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.876348Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !ls /content/competitions/cifar-10/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7e98794d-5e7e-4449-8a76-ccdc093dfa0e",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.879414Z",
     "iopub.status.busy": "2025-01-21T12:01:34.879017Z",
     "iopub.status.idle": "2025-01-21T12:01:34.881396Z",
     "shell.execute_reply": "2025-01-21T12:01:34.880936Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.879396Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !unzip /content/competitions/cifar-10/cifar-10.zip # 解压"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "b5915ece-a6ef-4561-98bd-81475dd8a544",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.882121Z",
     "iopub.status.busy": "2025-01-21T12:01:34.881958Z",
     "iopub.status.idle": "2025-01-21T12:01:34.884238Z",
     "shell.execute_reply": "2025-01-21T12:01:34.883801Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.882103Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !ls\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "73f89e83-2d2a-43d4-bc27-d96c2604f05c",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.885054Z",
     "iopub.status.busy": "2025-01-21T12:01:34.884785Z",
     "iopub.status.idle": "2025-01-21T12:01:34.887059Z",
     "shell.execute_reply": "2025-01-21T12:01:34.886623Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.885036Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# %pip install py7zr\n",
    "# import py7zr\n",
    "# a =py7zr.SevenZipFile(r'./train.7z','r')\n",
    "# a.extractall(path=r'./competitions/cifar-10/')\n",
    "# a.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1c90f57d-d251-4000-94b9-416c7715c8f8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:34.889821Z",
     "iopub.status.busy": "2025-01-21T12:01:34.889533Z",
     "iopub.status.idle": "2025-01-21T12:01:35.066429Z",
     "shell.execute_reply": "2025-01-21T12:01:35.065896Z",
     "shell.execute_reply.started": "2025-01-21T12:01:34.889802Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "50000\n"
     ]
    }
   ],
   "source": [
    "!ls competitions/cifar-10/train/ |wc -l  #wc -l统计数量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "c61d7d30-e2ff-41fd-9214-e1f6bb82b3b9",
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:35.067226Z",
     "iopub.status.busy": "2025-01-21T12:01:35.067022Z",
     "iopub.status.idle": "2025-01-21T12:01:35.069631Z",
     "shell.execute_reply": "2025-01-21T12:01:35.069182Z",
     "shell.execute_reply.started": "2025-01-21T12:01:35.067203Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# a =py7zr.SevenZipFile(r'./test.7z','r')\n",
    "# a.extractall(path=r'./competitions/cifar-10/')\n",
    "# a.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "b4a58401-2e5b-408a-9de0-a604b4236690",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:35.070501Z",
     "iopub.status.busy": "2025-01-21T12:01:35.070242Z",
     "iopub.status.idle": "2025-01-21T12:01:35.675222Z",
     "shell.execute_reply": "2025-01-21T12:01:35.674632Z",
     "shell.execute_reply.started": "2025-01-21T12:01:35.070482Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "300000\n"
     ]
    }
   ],
   "source": [
    "!ls competitions/cifar-10/test/ |wc -l  #wc -l统计数量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1dad802b6500ab83",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:50.974368Z",
     "start_time": "2025-01-21T09:48:49.158128Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:35.676193Z",
     "iopub.status.busy": "2025-01-21T12:01:35.675973Z",
     "iopub.status.idle": "2025-01-21T12:01:37.880451Z",
     "shell.execute_reply": "2025-01-21T12:01:37.879918Z",
     "shell.execute_reply.started": "2025-01-21T12:01:35.676170Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# 目的：通过读取csv文件，得到图片的类别，并将图片路径和类别保存到DataFrame中。\n",
    "\n",
    "DATA_DIR1 = Path(\"./\")\n",
    "DATA_DIR2=Path(\"./competitions/cifar-10/\")\n",
    "\n",
    "train_labels_file = DATA_DIR1 / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR1 / \"sampleSubmission.csv\"  # 测试集模板csv文件\n",
    "train_folder = DATA_DIR2 / \"train/\"\n",
    "test_folder = DATA_DIR2 / \"test/\"\n",
    "\n",
    "# 所有的类别\n",
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "\n",
    "# filepath:csv文件路径，folder:图片所在文件夹\n",
    "def parse_csv_file(filepath, folder):\n",
    "    result = []\n",
    "    # 读取所有行\n",
    "    # with 语句：用于管理文件的上下文。在 with 块结束时，文件会自动关闭，无需手动调用 f.close()。\n",
    "    with open(filepath, 'r') as f:\n",
    "        # f.readlines()：读取文件的所有行，返回一个列表，每个元素是一行内容。\n",
    "        # 第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        # strip('\\n')：去除行末的换行符（\\n）。\n",
    "        # split(',')：按','分割字符串，返回一个列表。\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        # 得到图片的路径\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        # 得到对应图片的路径和分类\n",
    "        result.append((image_full_path, label_str))\n",
    "    return result\n",
    "\n",
    "\n",
    "# 得到训练集和测试集的图片路径和分类\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "#打印\n",
    "import pprint\n",
    "\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "8cd5063c39c678cf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.050040Z",
     "start_time": "2025-01-21T09:48:50.975373Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:37.881277Z",
     "iopub.status.busy": "2025-01-21T12:01:37.881066Z",
     "iopub.status.idle": "2025-01-21T12:01:37.941039Z",
     "shell.execute_reply": "2025-01-21T12:01:37.940399Z",
     "shell.execute_reply.started": "2025-01-21T12:01:37.881256Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 取前45000张图片作为训练集\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "# 取后5000张图片作为验证集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "# 测试集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "# 为 Pandas DataFrame 的列重新命名\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "af9444393c2debca",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.057786Z",
     "start_time": "2025-01-21T09:48:51.051043Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:37.941811Z",
     "iopub.status.busy": "2025-01-21T12:01:37.941630Z",
     "iopub.status.idle": "2025-01-21T12:01:40.047592Z",
     "shell.execute_reply": "2025-01-21T12:01:40.047044Z",
     "shell.execute_reply.started": "2025-01-21T12:01:37.941792Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    # enumerate(class_names)：遍历 class_names，返回索引和类别名称的元组\n",
    "    # 将类别名称映射为索引，通常用于将标签转换为模型可以处理的数值。\n",
    "    label_to_idx = {\n",
    "        label: idx for idx, label in enumerate(class_names)\n",
    "    }\n",
    "    # 将索引映射为类别名称，通常用于将模型的输出（索引）转换回可读的类别名称\n",
    "    idx_to_label = {\n",
    "        idx: label for idx, label in enumerate(class_names)\n",
    "    }\n",
    "\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 获取对应模式的df，不同字符串对应不同模式\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(f\"Invalid mode: {mode}\")\n",
    "        self.transform = transform\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        # 获取图片路径和标签\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        # 打开一张图片并将其转换为 RGB 格式\n",
    "        img = Image.open(img_path).convert('RGB')  # 确保图片具有三个通道\n",
    "        # 应用数据增强\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回df的行数,样本数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "#数据增强\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 缩放\n",
    "    transforms.RandomRotation(40),  # 随机旋转\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n",
    "    transforms.ToTensor(),  # 转换为Tensor\n",
    "    transforms.Normalize(mean, std)  # 标准化\n",
    "])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])\n",
    "\n",
    "train_ds = Cifar10Dataset(mode=\"train\", transform=transforms_train)\n",
    "eval_ds = Cifar10Dataset(mode=\"eval\", transform=transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "d17ef4a56f0b6ae1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.065134Z",
     "start_time": "2025-01-21T09:48:51.059798Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.048793Z",
     "iopub.status.busy": "2025-01-21T12:01:40.048251Z",
     "iopub.status.idle": "2025-01-21T12:01:40.119923Z",
     "shell.execute_reply": "2025-01-21T12:01:40.119467Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.048771Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 32, 32])"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "161282a4e612afcd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.070229Z",
     "start_time": "2025-01-21T09:48:51.066136Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.120669Z",
     "iopub.status.busy": "2025-01-21T12:01:40.120489Z",
     "iopub.status.idle": "2025-01-21T12:01:40.123751Z",
     "shell.execute_reply": "2025-01-21T12:01:40.123289Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.120650Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size,\n",
    "                          shuffle=True)\n",
    "eval_loader = DataLoader(eval_ds, batch_size=batch_size,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b5b31416450980",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "b73daeeb83b4b527",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.077455Z",
     "start_time": "2025-01-21T09:48:51.071233Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.124629Z",
     "iopub.status.busy": "2025-01-21T12:01:40.124341Z",
     "iopub.status.idle": "2025-01-21T12:01:40.130852Z",
     "shell.execute_reply": "2025-01-21T12:01:40.130397Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.124610Z"
    }
   },
   "outputs": [],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes=10):\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=128, kernel_size=3, padding=\"same\"),  # 输出尺寸（128，32，32）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(128),  # 批标准化，在通道数做的归一化\n",
    "            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\"),  #输出尺寸（128，32，32）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(128),\n",
    "            nn.MaxPool2d(kernel_size=2),  #输出尺寸（128，16，16）\n",
    "            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=\"same\"),  #输出尺寸（256，16，16）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=\"same\"),  #输出尺寸（256，16，16）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.MaxPool2d(kernel_size=2),  #输出尺寸（256，8，8）\n",
    "            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=\"same\"),  #输出尺寸（512，8，8）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(512),\n",
    "            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=\"same\"),  #输出尺寸（512，8，8）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(512),\n",
    "            nn.MaxPool2d(kernel_size=2),  #输出尺寸（512，4，4）\n",
    "            nn.Flatten(),  #展平\n",
    "            nn.Linear(8192, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(512, num_classes),\n",
    "        )  # Sequential自动连接各层，把各层的输出作为下一层的输入\n",
    "\n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "\n",
    "# \n",
    "# for key, value in CNN(len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "b294d25e7f6287d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.120032Z",
     "start_time": "2025-01-21T09:48:51.078460Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.131532Z",
     "iopub.status.busy": "2025-01-21T12:01:40.131355Z",
     "iopub.status.idle": "2025-01-21T12:01:40.203875Z",
     "shell.execute_reply": "2025-01-21T12:01:40.203418Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.131514Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 8779914\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in CNN(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ca49d0dca74376",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "632d31ce33563b0c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.226894Z",
     "start_time": "2025-01-21T09:48:51.121036Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.204592Z",
     "iopub.status.busy": "2025-01-21T12:01:40.204420Z",
     "iopub.status.idle": "2025-01-21T12:01:40.305575Z",
     "shell.execute_reply": "2025-01-21T12:01:40.305099Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.204574Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "28c2ba49f4ff546",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.233986Z",
     "start_time": "2025-01-21T09:48:51.227901Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.306587Z",
     "iopub.status.busy": "2025-01-21T12:01:40.306127Z",
     "iopub.status.idle": "2025-01-21T12:01:40.311565Z",
     "shell.execute_reply": "2025-01-21T12:01:40.311090Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.306566Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"06_cifar_best.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "cd4d61586ad24eb5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.240769Z",
     "start_time": "2025-01-21T09:48:51.235992Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.312367Z",
     "iopub.status.busy": "2025-01-21T12:01:40.312100Z",
     "iopub.status.idle": "2025-01-21T12:01:40.316281Z",
     "shell.execute_reply": "2025-01-21T12:01:40.315805Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.312347Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "725cf8a521379801",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.249019Z",
     "start_time": "2025-01-21T09:48:51.241774Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.317181Z",
     "iopub.status.busy": "2025-01-21T12:01:40.316986Z",
     "iopub.status.idle": "2025-01-21T12:01:40.324781Z",
     "shell.execute_reply": "2025-01-21T12:01:40.324324Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.317149Z"
    }
   },
   "outputs": [],
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "22bf1e82c9715088",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:48:51.290855Z",
     "start_time": "2025-01-21T09:48:51.250023Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.325571Z",
     "iopub.status.busy": "2025-01-21T12:01:40.325326Z",
     "iopub.status.idle": "2025-01-21T12:01:40.391323Z",
     "shell.execute_reply": "2025-01-21T12:01:40.390878Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.325552Z"
    }
   },
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = CNN(len(class_names))  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用 adam\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "5335f62fe5e4977f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T09:50:14.731953Z",
     "start_time": "2025-01-21T09:48:51.291859Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:01:40.391991Z",
     "iopub.status.busy": "2025-01-21T12:01:40.391829Z",
     "iopub.status.idle": "2025-01-21T12:16:56.060570Z",
     "shell.execute_reply": "2025-01-21T12:16:56.059926Z",
     "shell.execute_reply.started": "2025-01-21T12:01:40.391974Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 27%|██▋       | 19008/70400 [15:15<41:15, 20.76it/s, epoch=26]  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 27 / global_step 19008\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    eval_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "1a790e5f4635d5e8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:16:56.061600Z",
     "iopub.status.busy": "2025-01-21T12:16:56.061332Z",
     "iopub.status.idle": "2025-01-21T12:16:56.256368Z",
     "shell.execute_reply": "2025-01-21T12:16:56.255871Z",
     "shell.execute_reply.started": "2025-01-21T12:16:56.061578Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    # print(train_df)\n",
    "    # print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1] + 1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9729d15a0d01e8",
   "metadata": {},
   "source": [
    "## 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "3195e10a60c29806",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:16:56.257137Z",
     "iopub.status.busy": "2025-01-21T12:16:56.256945Z",
     "iopub.status.idle": "2025-01-21T12:16:58.401400Z",
     "shell.execute_reply": "2025-01-21T12:16:58.400875Z",
     "shell.execute_reply.started": "2025-01-21T12:16:56.257116Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.4487, Test acc: 0.8806\n"
     ]
    }
   ],
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/06_cifar_best.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, eval_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed53a71c536fd7ae",
   "metadata": {},
   "source": [
    "## 推理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "acea90c8a2f4438a",
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T12:16:58.402249Z",
     "iopub.status.busy": "2025-01-21T12:16:58.401977Z",
     "iopub.status.idle": "2025-01-21T12:19:10.269687Z",
     "shell.execute_reply": "2025-01-21T12:19:10.269186Z",
     "shell.execute_reply.started": "2025-01-21T12:16:58.402228Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>filepath</th>\n",
       "      <th>class</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>competitions/cifar-10/test/1.png</td>\n",
       "      <td>cat</td>\n",
       "      <td>deer</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>competitions/cifar-10/test/2.png</td>\n",
       "      <td>cat</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>competitions/cifar-10/test/3.png</td>\n",
       "      <td>cat</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>competitions/cifar-10/test/4.png</td>\n",
       "      <td>cat</td>\n",
       "      <td>ship</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>competitions/cifar-10/test/5.png</td>\n",
       "      <td>cat</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           filepath class       label\n",
       "0  competitions/cifar-10/test/1.png   cat        deer\n",
       "1  competitions/cifar-10/test/2.png   cat    airplane\n",
       "2  competitions/cifar-10/test/3.png   cat  automobile\n",
       "3  competitions/cifar-10/test/4.png   cat        ship\n",
       "4  competitions/cifar-10/test/5.png   cat    airplane"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_ds = Cifar10Dataset(mode=\"test\", transform=transforms_eval)\n",
    "# drop_last=False：最后一个批次可能不足 batch_size，不丢弃。\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False,\n",
    "                         drop_last=False)\n",
    "\n",
    "preds_collect = [] # 预测结果收集器\n",
    "model.eval()  # 评估模式\n",
    "for datas, labels in test_loader:\n",
    "    datas = datas.to(device)\n",
    "    # 前向传播\n",
    "    logits = model(datas)\n",
    "    # 得到预测类别，idx_to_label是id到字符串类别的映射\n",
    "    preds=[test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()]\n",
    "    preds_collect.extend(preds)\n",
    "\n",
    "test_df[\"label\"]=preds_collect # 增加预测结果列\n",
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "20e3f7f2-36e8-483c-a5a2-36f00a12b460",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:28:48.228923Z",
     "iopub.status.busy": "2025-01-21T12:28:48.228565Z",
     "iopub.status.idle": "2025-01-21T12:28:48.239766Z",
     "shell.execute_reply": "2025-01-21T12:28:48.239151Z",
     "shell.execute_reply.started": "2025-01-21T12:28:48.228898Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "test_df.drop(columns=['class'], inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "6a73549e-d18c-4f63-9ca5-f956c8104820",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:32:16.353536Z",
     "iopub.status.busy": "2025-01-21T12:32:16.353188Z",
     "iopub.status.idle": "2025-01-21T12:32:16.359566Z",
     "shell.execute_reply": "2025-01-21T12:32:16.359003Z",
     "shell.execute_reply.started": "2025-01-21T12:32:16.353512Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>deer</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "      <td>ship</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   id       label\n",
       "0   1        deer\n",
       "1   2    airplane\n",
       "2   3  automobile\n",
       "3   4        ship\n",
       "4   5    airplane"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "91cc34fb-f12b-4f70-9eca-2774ccc8846f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:30:22.736422Z",
     "iopub.status.busy": "2025-01-21T12:30:22.736056Z",
     "iopub.status.idle": "2025-01-21T12:30:22.739694Z",
     "shell.execute_reply": "2025-01-21T12:30:22.739184Z",
     "shell.execute_reply.started": "2025-01-21T12:30:22.736396Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "test_df.rename(columns={'filepath': 'id'}, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "abab0153-824f-4c47-945b-40104ae85aa1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:31:27.017588Z",
     "iopub.status.busy": "2025-01-21T12:31:27.017233Z",
     "iopub.status.idle": "2025-01-21T12:31:27.074219Z",
     "shell.execute_reply": "2025-01-21T12:31:27.073707Z",
     "shell.execute_reply.started": "2025-01-21T12:31:27.017564Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "test_df = pd.DataFrame(list(range(1,3*10**5+1)), columns=[\"id\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "af8f6623-8a34-48c7-996b-6021ff83b112",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:32:14.128564Z",
     "iopub.status.busy": "2025-01-21T12:32:14.128197Z",
     "iopub.status.idle": "2025-01-21T12:32:14.138361Z",
     "shell.execute_reply": "2025-01-21T12:32:14.137801Z",
     "shell.execute_reply.started": "2025-01-21T12:32:14.128540Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "test_df[\"label\"]=preds_collect # 增加预测结果列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "4fe342076fe7e45a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T12:32:21.039257Z",
     "iopub.status.busy": "2025-01-21T12:32:21.038895Z",
     "iopub.status.idle": "2025-01-21T12:32:21.213618Z",
     "shell.execute_reply": "2025-01-21T12:32:21.213093Z",
     "shell.execute_reply.started": "2025-01-21T12:32:21.039232Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "test_df.to_csv(\"submission.csv\", index=False) # 保存预测结果"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
